package com.raindrop.rd.exception;

import com.raindrop.rd.parser.Span;
import com.raindrop.rd.parser.token.TokenStream;

/**
 * RD语言源码错误提示
 *
 * @author BillDowney
 * @date 2021/3/23 16:45
 */
public class RDScriptError {

    /**
     * <p>
     * Create an error message based on the provided message and stream, highlighting the line on which the error happened. If the
     * stream has more tokens, the next token will be highlighted. Otherwise the end of the source of the stream will be
     * highlighted.
     * </p>
     *
     * <p>
     * Throws a {@link RuntimeException}
     * </p>
     */
    public static void error(String message, TokenStream stream) {
        if (stream.hasMore()) {
            error(message, stream.consume().getSpan());
        } else {
            error(message, stream.getPrev().getSpan());
        }
    }

    /**
     * Create an error message based on the provided message and location, highlighting the location in the line on which the
     * error happened. Throws a {@link RDScriptException}
     **/
    public static void error(String message, Span location, Throwable cause) {
        cause = unwrap(cause);
        if (cause instanceof RDScriptException) {
            RDScriptException mse = ((RDScriptException) cause);
            if (mse.getLocation() == null) {
                error(message, location, cause.getCause());
                return;
            }
            throw mse;
        }
        Span.Line line = location.getLine();
        StringBuilder errorMessage = new StringBuilder(message + " at Row:");
        errorMessage.append(line.getLineNumber()).append("~").append(line.getEndLineNumber()).append(",Col:");
        errorMessage.append(line.getStartCol()).append("~").append(line.getEndCol()).append("\n\n");
        errorMessage.append(line.getText());
        errorMessage.append("\n");
        int errorStart = location.getStart() - line.getStart();
        int errorEnd = errorStart + location.getText().length() - 1;
        for (int i = 0, n = line.getText().length(); i < n; i++) {
            boolean useTab = line.getText().charAt(i) == '\t';
            errorMessage.append(i >= errorStart && i <= errorEnd ? "^" : useTab ? "\t" : " ");
        }
        if (cause == null) {
            throw new RDScriptException(errorMessage.toString(), message, location);
        } else {
            throw new RDScriptException(errorMessage.toString(), message, cause, location);
        }
    }

    /**
     * Create an error message based on the provided message and location, highlighting the location in the line on which the
     * error happened. Throws a {@link RDScriptException}
     **/
    public static void error(String message, Span location) {
        error(message, location, null);
    }

    public static Throwable unwrap(Throwable root) {
        Throwable parent = root;
        while (parent != null) {
            if (parent instanceof RDScriptException) {
                root = parent;
            }
            parent = parent.getCause();
        }
        return root;
    }

}
